package com.timing.finder.util;

import com.timing.finder.framework.ScanBucketException;
import com.timing.finder.util.json.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * Redis的工具类，继承于前辈写的工具类，额外实现了对Redis击穿、雪崩、穿透的保护方法
 *
 * @Auther Huang Yongxiang
 * @Date 2021/09/27 10:36
 */
public class RedisUtil {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
    private static final String REDIS_LOCK_PREFIX = "redis_lock_";//redis锁的前缀

    /**
     * 获取 StringRedisTemplate
     *
     * @return org.springframework.data.redis.core.StringRedisTemplate
     * @Author liuwenjun
     * @Date 2019/3/11 10:18
     */
    public static StringRedisTemplate getStringRedisTemplate() {
        return (StringRedisTemplate) SpringUtil.getBean("stringRedisTemplate");
    }

    /**
     * 获取  RedisTemplate<'String, Object>
     *
     * @return org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object>
     * @Author liuwenjun
     * @Date 2019/3/11 10:41
     */
    @SuppressWarnings("unchecked")
    public static RedisTemplate<String, Object> getRedisTemplate() {
        return (RedisTemplate<String, Object>) SpringUtil.getBean("redisTemplate");
    }

    /**
     * 删除缓存
     * 根据key精确匹配删除
     *
     * @param key
     */
    @SuppressWarnings("unchecked")
    public static void del(String... key) {
        if (key == null || key.length == 0) {
            return;
        }
        if (key.length == 1) {
            getRedisTemplate().delete(key[0]);
        } else {
            getRedisTemplate().delete(CollectionUtils.arrayToList(key));
        }
    }

    /**
     * 批量删除 （该操作会执行模糊查询，请尽量不要使用，以免影响性能或误删）
     *
     * @param pattern 键
     */
    public static void batchDel(String... pattern) {
        for (String kp : pattern) {
            Set<String> keys = getKeys(kp + "*");
            if (keys != null && keys.size() > 0) {
                int i = 1;
                for (String key : keys) {
                    logger.info("删除key" + i + "==" + key);
                    i++;
                }
                getRedisTemplate().delete(keys);
            }
        }
    }

    /**
     * 取得缓存（int型）
     *
     * @param key 键
     * @return Integer类型的value
     */
    public static Integer getInt(String key) {
        String value = getStringRedisTemplate()
                .boundValueOps(key)
                .get();
        if (StringUtils.isNotBlank(value)) {
            return Integer.valueOf(value);
        }
        return null;
    }

    /**
     * 取得缓存（字符串类型）
     *
     * @param key 键
     * @return String 值
     */
    public static String getStr(String key) {
        return getStringRedisTemplate()
                .boundValueOps(key)
                .get();
    }

    /**
     * 取得缓存（字符串类型）
     *
     * @param key    键
     * @param retain 获取后是否保留
     * @return 字符类型的 值
     */
    public static String getStr(String key, boolean retain) {
        String value = getStringRedisTemplate()
                .boundValueOps(key)
                .get();
        if (!retain) {
            getRedisTemplate().delete(key);
        }
        return value;
    }

    /**
     * 获取缓存<br>
     * 注：基本数据类型(Character除外)，请直接使用get(String key, Class<T> clazz)取值
     *
     * @param key 键
     * @return Object
     */
    public static Object getObj(String key) {
        return getRedisTemplate()
                .boundValueOps(key)
                .get();
    }

    /**
     * 获取缓存<br>
     * 注：java 8种基本类型的数据请直接使用get(String key, Class<T> clazz)取值
     *
     * @param key    键
     * @param retain 获取后 是否保留
     * @return Object
     */
    public static Object getObj(String key, boolean retain) {
        Object obj = getRedisTemplate()
                .boundValueOps(key)
                .get();
        if (!retain) {
            getRedisTemplate().delete(key);
        }
        return obj;
    }

    /**
     * 获取缓存
     * 注：该方法暂不支持Character数据类型
     *
     * @param key 键
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        return (T) getRedisTemplate()
                .boundValueOps(key)
                .get();
    }


    /**
     * 将value对象写入缓存
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, Object value) {

        if (value
                .getClass()
                .equals(String.class) || value
                .getClass()
                .equals(Integer.class) || value
                .getClass()
                .equals(Double.class) || value
                .getClass()
                .equals(Float.class) || value
                .getClass()
                .equals(Short.class) || value
                .getClass()
                .equals(Long.class) || value
                .getClass()
                .equals(Boolean.class)) {
            getStringRedisTemplate()
                    .opsForValue()
                    .set(key, value.toString());
        } else {
            getRedisTemplate()
                    .opsForValue()
                    .set(key, value);
        }
    }

    /**
     * 递减操作
     *
     * @param key 键
     * @param by  具体数值
     * @return 递减后的值
     */
    public static Double decr(String key, double by) {
        return getRedisTemplate()
                .opsForValue()
                .increment(key, -by);
    }

    /**
     * 递增操作
     *
     * @param key 键
     * @param by  具体数值
     * @return 递增后的值
     */
    public static Double incr(String key, double by) {
        return getRedisTemplate()
                .opsForValue()
                .increment(key, by);
    }

    /**
     * 获取double类型值
     *
     * @param key 键
     * @return double类似的数据
     */
    public static double getDouble(String key) {
        String value = getStringRedisTemplate()
                .boundValueOps(key)
                .get();
        if (StringUtils.isNotBlank(value)) {
            return Double.parseDouble(value);
        }
        return 0d;
    }

    /**
     * 设置double类型值
     *
     * @param key   键
     * @param value 值
     */
    public static void setDouble(String key, double value) {
        getStringRedisTemplate()
                .opsForValue()
                .set(key, String.valueOf(value));
    }

    /**
     * 设置Int类型值
     *
     * @param key   键
     * @param value 值
     */
    public static void setInt(String key, int value) {
        getStringRedisTemplate()
                .opsForValue()
                .set(key, String.valueOf(value));
    }

    /**
     * 将map写入缓存
     *
     * @param key 键
     * @param map map对象
     */
    public static <T> void setMap(String key, Map<String, T> map) {
        getRedisTemplate()
                .opsForHash()
                .putAll(key, map);
    }

    /**
     * 向key对应的map中添加缓存对象
     *
     * @param key 键
     * @param map map对象
     */
    public static <T> void addMap(String key, Map<String, T> map) {
        getRedisTemplate()
                .opsForHash()
                .putAll(key, map);
    }

    /**
     * 向key对应的map中添加缓存对象
     *
     * @param key   cache对象key
     * @param field map对应的key
     * @param value 值
     */
    public static void addMap(String key, String field, String value) {
        getRedisTemplate()
                .opsForHash()
                .put(key, field, value);
    }

    /**
     * 向key对应的map中添加缓存对象
     *
     * @param key   cache对象key
     * @param field map对应的key
     * @param obj   对象
     */
    public static <T> void addMap(String key, String field, T obj) {
        getRedisTemplate()
                .opsForHash()
                .put(key, field, obj);
    }

    /**
     * 获取map缓存
     *
     * @param key 键
     * @return map对象
     */
    public static <T> Map<String, T> mapGet(String key) {
        BoundHashOperations<String, String, T> boundHashOperations = getRedisTemplate().boundHashOps(key);
        return boundHashOperations.entries();
    }

    /**
     * 获取map缓存中的某个对象
     *
     * @param key   键
     * @param field map对应的key
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getMapField(String key, String field) {
        return (T) getRedisTemplate()
                .boundHashOps(key)
                .get(field);
    }

    /**
     * 删除map中的某个对象
     *
     * @param key   缓存的键
     * @param field map中该对象的key
     */
    public void delMapField(String key, String... field) {
        BoundHashOperations<String, String, ?> boundHashOperations = getRedisTemplate().boundHashOps(key);
        boundHashOperations.delete(field);
    }

    /**
     * 添加set
     *
     * @param key   键
     * @param value 值
     */
    public static void sadd(String key, String... value) {
        getRedisTemplate()
                .boundSetOps(key)
                .add(value);
    }

    /**
     * 删除set集合中的对象
     *
     * @param key   键
     * @param value 值
     */
    public static void sremove(String key, String... value) {
        getRedisTemplate()
                .boundSetOps(key)
                .remove(value);
    }

    /**
     * set重命名
     *
     * @param oldkey 原来的键
     * @param newkey 新命名的键
     */
    public static void srename(String oldkey, String newkey) {
        getRedisTemplate()
                .boundSetOps(oldkey)
                .rename(newkey);
    }


    /**
     * 模糊查询keys
     *
     * @param pattern 键
     * @return 符合条件的key 集合
     */
    public static Set<String> keys(String pattern) {
        return getRedisTemplate().keys(pattern);
    }

    /**
     * expire:(使缓存在指定时间过期失效)
     *
     * @param key 键
     */
    public static void expire(String key) {
        if (StringUtils.isBlank(key)) {
            return;
        }
        getRedisTemplate().expire(key, 1, TimeUnit.SECONDS);
    }

    /**
     * getConnection:(获取jedis连接). <br/>
     *
     * @return RedisConnection
     */
    public static RedisConnection getConnection() {
        RedisConnection redisConnection = null;
        RedisConnectionFactory redisConnectionFactory = getRedisTemplate().getConnectionFactory();
        if (redisConnectionFactory != null) {
            redisConnection = redisConnectionFactory.getConnection();
        }
        return redisConnection;
    }

    /**
     * 返回Keys
     *
     * @param partern
     * @return
     */
    public static Set<String> getKeys(String partern) {
        if (StringUtils.isBlank(partern)) {
            return null;
        }
        return getRedisTemplate().keys(partern);
    }


    /**
     * 将对象添加到缓存的列表中 采用末尾添加的方式
     *
     * @param key    键
     * @param object 需要添加的对象
     */
    public static void listPush(String key, Object object) {
        getRedisTemplate()
                .opsForList()
                .rightPush(key, object);
    }

    /**
     * 批量把一个集合插入到列表中 采用末尾添加
     *
     * @param key    键
     * @param values 待插入的列表
     */
    public static void listPushAll(String key, Collection<Object> values) {
        getRedisTemplate()
                .opsForList()
                .rightPushAll(key, values);
    }

    /**
     * 批量把一个数组插入到列表中
     *
     * @param key    键
     * @param values 数组
     */
    public static void listPushAll(String key, String[] values) {
        getRedisTemplate()
                .opsForList()
                .rightPushAll(key, values);
    }

    /**
     * 只有存在key对应的列表才能将这个value值插入到key所对应的列表中
     * 采用末尾添加
     *
     * @param key   键
     * @param value 对象
     * @return java.lang.Long 操作后列表的长度
     */
    public static Long listPushIfPresent(String key, Object value) {
        return getRedisTemplate()
                .opsForList()
                .rightPushIfPresent(key, value);
    }

    /**
     * 在列表中index的位置设置value值
     *
     * @param key   键
     * @param index 索引
     * @param value 对象
     */
    public static void listSet(String key, long index, Object value) {
        getRedisTemplate()
                .opsForList()
                .set(key, index, value);
    }

    /**
     * 从存储在键中的列表中删除等于值的元素的第一个计数事件。
     * 计数参数以下列方式影响操作：
     * count> 0：删除等于从头到尾移动的值的元素。
     * count <0：删除等于从尾到头移动的值的元素。
     * count = 0：删除等于value的所有元素。
     *
     * @param key   键
     * @param count 计数参数
     * @param value 需要删除的值
     * @return java.lang.Long
     */
    public static Long listRemove(String key, long count, Object value) {
        return getRedisTemplate()
                .opsForList()
                .remove(key, count, value);
    }

    /**
     * 根据下标获取列表中的值，下标是从0开始的
     *
     * @param key   键
     * @param index 索引
     * @return java.lang.Object
     */
    public static Object listIndex(String key, long index) {
        return getRedisTemplate()
                .opsForList()
                .index(key, index);
    }


    /**
     * 获取缓存中的列表 (全量返回)
     *
     * @param key
     * @return java.util.List<java.lang.Object>
     */
    public static List<Object> getList(String key) {
        return getRedisTemplate()
                .opsForList()
                .range(key, 0, -1);
    }

    /**
     * 修剪现有列表，使其只包含指定的指定范围的元素，起始和停止都是基于0的索引
     *
     * @param key   键
     * @param start 起始索引
     * @param end   停止索引
     */
    public static void listTrim(String key, long start, long end) {
        getRedisTemplate()
                .opsForList()
                .trim(key, start, end);
    }

    /**
     * 返回存储在键中的列表的长度。如果键不存在，则将其解释为空列表，并返回0。当key存储的值不是列表时会返回错误。
     *
     * @param key 键
     * @return java.lang.Long
     */
    public static Long listSize(String key) {
        return getRedisTemplate()
                .opsForList()
                .size(key);
    }

    /**
     * 指定缓存有效时间
     *
     * @param key  缓存名
     * @param time 时间，单位为秒
     * @return boolean 是否设置成功
     * @author Huang Yongxiang
     * @date 2021/9/27 15:02
     */
    public static boolean setValidTime(String key, long time) {
        return setValidTime(key, time, TimeUnit.SECONDS);
    }

    public static boolean setValidTime(String key, long time, TimeUnit unit) {
        try {
            if (time > 0) {
                getRedisTemplate().expire(key, time, unit);
            }
            return true;
        } catch (Exception e) {
            logger.error("错误！设置缓存{}有效时间失败！", key);
            e.printStackTrace();
            return false;
        }
    }

    public static boolean setStringValidTime(String key, long time, TimeUnit unit) {
        try {
            if (time > 0) {
                getStringRedisTemplate().expire(key, time, unit);
            }
            return true;
        } catch (Exception e) {
            logger.error("错误！设置缓存{}有效时间失败！", key);
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 获取缓存的有效时间：毫秒
     *
     * @param key 缓存名
     * @return long -1：不存在有效时间;-2：不存在该key;-3：特殊异常
     * @author Huang Yongxiang
     * @date 2021/9/27 15:11
     */
    public static long getValidTime(String key) {
        RedisTemplate<String, Object> redisTemplate = getRedisTemplate();
        Long value = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
        return value != null ? value : -1;
    }

    /**
     * 给Redis上锁
     *
     * @param keyName        锁名
     * @param systemTime     系统时间戳
     * @param validTime      锁生存时间：毫秒
     * @param maxTryTime     上锁失败后尝试获取最大时间
     * @param sleepBeforeTry 获取失败后休眠时间
     * @return boolean
     * @author Huang Yongxiang
     * @date 2021/9/30 14:25
     */
    public static boolean lock(String keyName, long systemTime, long validTime, long maxTryTime, long sleepBeforeTry) {

        keyName = REDIS_LOCK_PREFIX + keyName;
        while (true) {
            StringRedisTemplate stringRedisTemplate = getStringRedisTemplate();
            Boolean flag = stringRedisTemplate
                    .opsForValue()
                    .setIfAbsent(keyName, String.valueOf(validTime));
            if (flag != null && flag) {
                if (setStringValidTime(keyName, validTime, TimeUnit.SECONDS)) {
                    logger.debug("加锁成功，锁名：{}", keyName);
                    return true;
                }
            }
//            //设置失败，获得锁失败
//            // 判断锁超时 - 防止原来的操作异常，没有运行解锁操作 ，防止死锁
//            String currentLock = stringRedisTemplate.opsForValue().get(keyName);
//            // 如果锁过期 currentLock不为空且小于当前时间
//            if (!StringUtils.isEmpty(currentLock) && systemTime + Long.parseLong(currentLock) < System.currentTimeMillis()) {
//                //如果lockKey对应的锁已经存在，获取上一次设置的时间戳之后并重置lockKey对应的锁的时间戳
//                String preLock = stringRedisTemplate.opsForValue().getAndSet(keyName, validTime + "");
//                if (!StringUtils.isEmpty(preLock) && preLock.equals(currentLock)) {
//                    if (setValidTime(keyName, validTime)) {
//                        logger.debug("加锁成功，锁名：{}", keyName);
//                        return true;
//                    }
//                }
//            }
            logger.debug(Thread
                    .currentThread()
                    .getName() + "加锁失败！{}ms后进行重新获取...", sleepBeforeTry);
            try {
                Thread.sleep(sleepBeforeTry);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (systemTime + maxTryTime < System.currentTimeMillis()) {
                logger.debug(Thread
                        .currentThread()
                        .getName() + "加锁最大尝试时间结束，未获取到锁！");
                break;
            }
        }


        return false;
    }

    /**
     * 释放锁
     *
     * @param key 锁名
     * @return boolean
     * @author Huang Yongxiang
     * @date 2021/9/27 17:09
     */
    public static boolean release(String key) {
        try {
            getStringRedisTemplate().delete(REDIS_LOCK_PREFIX + key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        logger.debug("Redis锁:{}已被删除！", key);
        return true;
    }

    /**
     * 更新缓存值但不刷新过期时间
     *
     * @param key   缓存名
     * @param value 缓存值
     * @return boolean
     * @author Huang Yongxiang
     * @date 2021/9/29 11:10
     */
    public static boolean setNoRefreshValid(String key, String value) {
        try {
            getStringRedisTemplate()
                    .opsForValue()
                    .set(key, value, getValidTime(key), TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 更新缓存值同时刷新过期时间
     *
     * @param key   缓存名
     * @param value 缓存值
     * @param time  刷新到的时间：毫秒
     * @return boolean
     * @author Huang Yongxiang
     * @date 2021/9/29 11:12
     */
    public static boolean setAndRefreshValid(String key, String value, long time) {
        try {
            getStringRedisTemplate()
                    .opsForValue()
                    .set(key, value, time, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 开辟一个子线程对指定Redis锁进行监测，当其存活时间到达某个阈值时进行存活时间重置
     * 监测线程和业务线程不冲突，监测时业务线程可照常运行，本方法将会返回一个FutureTask对象
     * 该对象在监测结束后将会返回一个标志作为是否监测成功的判断。监听时间应该大于锁的生命
     * 周期，重置阈值请根据实际指定，由于重置时间会消耗一定时间，阈值过小时将会导致锁过期
     *
     * @param lockName   监测的锁名
     * @param percent    重置阈值：0-1，当剩余存活时间低于该阈值时进行存活时间重置
     * @param listenTime 监听时长：毫秒
     * @return java.util.concurrent.FutureTask
     * @author Huang Yongxiang
     * @date 2021/9/29 11:25
     */
    public static FutureTask<Object> listenLock(String lockName, double percent, long listenTime) {

        Callable<Object> callable = () -> {
            logger.info("监听器启动，监听lock：{}，预计监听时长：{}ms...", lockName, listenTime);
            long start = System.currentTimeMillis();
            int count = 0;
            while (true) {
                //logger.info("监听器第{}轮监听...", count++);
                long now = System.currentTimeMillis();
                if (start + listenTime < now) {
                    logger.debug("监听结束，本次监听时长：{}ms", listenTime);
                    return true;
                }
                //目前生存时间
                long valid = getValidTime(REDIS_LOCK_PREFIX + lockName);
                //如果锁已过期或锁已被释放则不再处理
                if (valid == -1 || valid == -2) {
                    logger.error("锁已过期，监听失败");
                    return false;
                }
                //原始生存时间
                long time = Long.parseLong(getStr(REDIS_LOCK_PREFIX + lockName));

                if (getValidTime(REDIS_LOCK_PREFIX + lockName) <= percent * time && getValidTime(REDIS_LOCK_PREFIX + lockName) > 0) {
                    if (setAndRefreshValid(REDIS_LOCK_PREFIX + lockName, time + "", time)) {
                        logger.debug("达到阈值,对lock:{}进行过期时间重置到{}ms，目前剩余生命时间{}ms!", lockName, time, getValidTime(REDIS_LOCK_PREFIX + lockName));
                    }
                }
            }
        };
        FutureTask<Object> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        return futureTask;
    }

    public static void setList(Map<String, String> values) {
        getRedisTemplate()
                .opsForValue()
                .multiSet(values);
    }

    public static void deleteStr(String... keys) {
        if (keys == null || keys.length == 0) {
            return;
        }
        if (keys.length == 1) {
            getStringRedisTemplate().delete(keys[0]);
        } else {
            getStringRedisTemplate().delete(Arrays.asList(keys));
        }
    }

    public static void setList(Map<String, String> values, long expiringTime, TimeUnit unit) {
        getRedisTemplate().executePipelined((RedisCallback<Object>) connection -> {
            try {
                connection.openPipeline();
                for (Map.Entry<String, String> entry : values.entrySet()) {
                    connection.set(entry
                            .getKey()
                            .getBytes(), entry
                            .getValue()
                            .getBytes(), Expiration.from(expiringTime, unit), RedisStringCommands.SetOption.UPSERT);
                }
            } finally {
                connection.closePipeline();
            }
            return null;
        });
    }

    public static <T> List<T> getJsonList(List<String> keys, Class<T> type) {
        List<String> valuesList = getStringRedisTemplate()
                .opsForValue()
                .multiGet(keys);
        List<T> result = new ArrayList<>();
        if (!CollectionUtils.isEmpty(valuesList)) {
            for (String value : valuesList) {
                if (!StringUtils.isEmpty(value)) {
                    try {
                        result.add(JsonUtil.jsonStringToPojo(value, type));
                    } catch (Exception ignored) {
                    }
                }
            }
        }
        return result;
    }

    public static Long stringListPushAll(String key, Collection<String> values) {
        return getStringRedisTemplate()
                .opsForList()
                .rightPushAll(key, values);
    }

    public static Long stringListPushAll(String key, Collection<String> values, long expiringTime, TimeUnit unit) {
        Long length = getStringRedisTemplate()
                .opsForList()
                .rightPushAll(key, values);
        getStringRedisTemplate().expire(key, expiringTime, unit);
        return length;
    }

    public static List<String> getStringList(String key) {
        return getStringRedisTemplate()
                .opsForList()
                .range(key, 0, -1);
    }

    public static Long stringSetPushAll(String key, Collection<String> values) {
        return getStringRedisTemplate()
                .opsForSet()
                .add(key, values.toArray(new String[0]));
    }

    public static Long stringSetPushAll(String key, long expiringTime, TimeUnit unit, Collection<String> values) {
        Long length = stringSetPushAll(key, values);
        getStringRedisTemplate().expire(key, expiringTime, unit);
        return length;
    }

    public static long getStringValidTime(String key) {
        Long value = getStringRedisTemplate().getExpire(key, TimeUnit.MILLISECONDS);
        return value != null ? value : -1;
    }

    public static Set<String> getStringSet(String key) {
        return getStringRedisTemplate()
                .opsForSet()
                .members(key);
    }


    /**
     * 随机休眠时间后进行get操作
     *
     * @param key          缓存名
     * @param minSleepTime 随机最小时间：毫秒
     * @param maxSleepTime 随机最长时间：毫秒
     * @return java.lang.String
     * @author Huang Yongxiang
     * @date 2021/10/2 14:37
     */
    public static String randomGet(String key, int minSleepTime, int maxSleepTime) {
        Random random = new Random();
        long sleep = random.nextInt(minSleepTime + 1) + (maxSleepTime - minSleepTime);
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getStr("key");
    }

    @SuppressWarnings("unchecked")
    public static Set<String> scanKeys(String pattern) {
        Set<String> keys = new LinkedHashSet<>();
        try {
            ScanOptions options = ScanOptions
                    .scanOptions()
                    .count(Integer.MAX_VALUE)
                    .match(pattern)
                    .build();
            RedisSerializer<String> redisSerializer = (RedisSerializer<String>) getRedisTemplate().getKeySerializer();
            Cursor<String> cursor = getRedisTemplate().executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
            if (cursor != null) {
                while (cursor.hasNext()) {
                    keys.add(cursor.next());
                }
                cursor.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return keys;
    }

    public static Map<String, Boolean> scanKeysExist(List<String> keys) {
        Map<String, Boolean> exist = new HashMap<>();
        keys.forEach(key -> exist.put(key, false));
        int count = 0;
        try {
            ScanOptions options = ScanOptions
                    .scanOptions()
                    .count(Integer.MAX_VALUE)
                    .build();
            RedisSerializer<String> redisSerializer = (RedisSerializer<String>) RedisUtil
                    .getRedisTemplate()
                    .getKeySerializer();
            Cursor<String> cursor = RedisUtil
                    .getRedisTemplate()
                    .executeWithStickyConnection(redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
            if (cursor != null) {
                while (cursor.hasNext()) {
                    count++;
                    String key = "";
                    try {
                        key = cursor.next();
                    } catch (Exception ignored) {
                        continue;
                    }
                    if (exist.containsKey(key)) {
                        exist.put(key, true);
                    }
                }
                cursor.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ScanBucketException("扫描桶时发生异常" + e.getMessage());
        }
        System.out.println("累计扫描" + count + "个key");
        return exist;
    }

    public static Set<String> stringKeys(String pattern) {
        return getStringRedisTemplate().keys(pattern);
    }

    /**
     * 随机休眠时间后进行set操作
     *
     * @param key          缓存名
     * @param value        缓存值
     * @param minSleepTime 随机最小时间：毫秒
     * @param maxSleepTime 随机最大时间：毫秒
     * @return void
     * @author Huang Yongxiang
     * @date 2021/10/2 17:01
     */
    public static void randomSet(String key, Object value, int minSleepTime, int maxSleepTime) {
        Random random = new Random();
        long sleep = random.nextInt(minSleepTime + 1) + (maxSleepTime - minSleepTime);
        try {
            Thread.sleep(sleep);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        set(key, value);
    }

}
